/*  Copyright (C) 2008  Alexander Blyzniuchenko (BlezAlex)

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

using System;
using System.Diagnostics;
using System.Text;
using System.Net.Sockets;
using Microsoft.VisualBasic;


namespace DC
{
    public enum CommandType { Chat, PM, Lock, Hello, NickList, Unknow, Search, MyNick, FileLength, ADCGET, ADCSND, Error, Quit, MyInfo, OpList, UserIP, MaxedOut};
    
    public abstract class BaseDcClient : IDisposable 
    {
        private string _commandBuffer;
        private const int _bufferSize = 1024;
        private readonly byte[] _buffer = new byte[_bufferSize];
        private int _currentCommandIndex;
        private string[] _currentCommands;
        
        protected NetworkStream _netStream;
        protected static Encoding _winEncoding = Encoding.GetEncoding("windows-1251");
        protected IAsyncResult _callResult;

        ~BaseDcClient()
        {
            Dispose();
        }

        public void Dispose()
        {
            if (_netStream != null)
                _netStream.Dispose();
            GC.SuppressFinalize(this);
        }

        protected abstract bool ProcessCommand(string command);
        
        protected void StartReadData()
        {
            _callResult = _netStream.BeginRead(_buffer, 0, _buffer.Length, ReadCallback, null);
        }
        
        protected internal virtual CommandType GetCommandType(string command)
        {
            if (command.Contains("$Lock "))
                return CommandType.Lock;
            
            return CommandType.Unknow;
        }
        
        protected void SendCommand(string command)
        {
			Debug.WriteLine(command);
        	byte[] replyBytes = _winEncoding.GetBytes(command + "|");
            _netStream.Write(replyBytes, 0, replyBytes.Length);
        }
        
        protected void ProcessLockCommand(string commandContent)
        {
            string pk = commandContent.Substring(5, commandContent.IndexOf(" Pk=") - 5);
            SendCommand("$Key " + Decode(pk));       // send key reply
        }

        protected byte[] GetUnprocessedData()
        {
            string data = string.Empty;
            for (int i = _currentCommandIndex + 2; i < _currentCommands.Length; i++)
                data += _currentCommands[_currentCommandIndex];

            return _winEncoding.GetBytes(data);
        }

        private void ReadCallback(IAsyncResult result)
        {
            try
            {
                int nread = _netStream.EndRead(result);

                if (nread == 0)
                    return;

                _commandBuffer += _winEncoding.GetString(_buffer, 0, nread);
                _currentCommands = _commandBuffer.Split('|');
                int commandsCount;

                // if last command was complete we should clean command cache othervise store last part of command in cache
                if (_commandBuffer[_commandBuffer.Length - 1] == '|')
                {
                    _commandBuffer = "";
                    commandsCount = _currentCommands.Length;
                }
                else
                {
                    _commandBuffer = _currentCommands[_currentCommands.Length - 1];
                    commandsCount = _currentCommands.Length - 1; // last command has been not recived completely
                }

                for (_currentCommandIndex = 0; _currentCommandIndex < commandsCount; _currentCommandIndex++)
                {
                    try
                    {
                        if (IsCommandValid(_currentCommands[_currentCommandIndex]))
                            if (!ProcessCommand(_currentCommands[_currentCommandIndex]))
                            {
                                return;
                            }
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine(ex);
                    }
                }

                StartReadData();
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
        }

        private static bool IsCommandValid(string command)
        {
            return command.Length > 3 && (command[0] == '$' || command[0] == '<');
        } 

        private static string Decode(string aLock)
        {
            char[] key = new char[aLock.Length];

            for (int i = 1; i < aLock.Length; i++)
            {
                key[i] = Strings.Chr(Strings.Asc(aLock[i])
                    ^ Strings.Asc(aLock[i - 1]));
            }

            key[0] = Strings.Chr(Strings.Asc(aLock[0])
                ^ Strings.Asc(aLock[aLock.Length - 1])
                ^ Strings.Asc(aLock[aLock.Length - 2])
                ^ 5);

            for (int i = 0; i < aLock.Length; i++)
            {
                key[i] = Strings.Chr(
                    ((Strings.Asc(key[i]) << 4) & 240)
                    | ((Strings.Asc(key[i]) >> 4) & 15));
            }

            string keyString = "";
            for (int i = 0; i < key.Length; i++)
            {
                int j = Strings.Asc(key[i]);
                if (j != key[i])
                {
                    key[i] = (char)j;
                }
                keyString += key[i];
            }
            return escapeChars(keyString);
        }

        private static string escapeChars(string key)
        {
            StringBuilder builder = new StringBuilder(key.Length);

            for (int index = 0; index < key.Length; index++)
            {
                int code = key[index];
                if (code == 0 || code == 5 || code == 36 || code == 96
                        || code == 124 || code == 126)
                    builder.AppendFormat("/%DCN{0:000}%/", code);
                else
                    builder.Append(key[index]);
            }

            return builder.ToString();
        }


    }
}
